package com.chapter1;

import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static java.util.concurrent.ThreadLocalRandom.current;

/*
简单理解CyclicBarrier的实现原理：

初始化CyclicBarrier，传入一个int参数，表示分片（parites），通常意义上来说分片数就是任务的数量
同时串行化执行多个任务
任务执行完成后，调用await()，等待其他线程也到达barrier point
当所有线程到达后，继续以串行化方式运行任务
常见的使用方法是设置分片数为任务数+1，这样，可以在主线程中执行await()，等待所有子任务完成。比如下面是使用CyclicBarrier实现同样功能的模拟代码：

await()方法会等待所有的线程到达barrier point，上面代码执行流程简述如下：

初始化CyclicBarrier，分片数为11（子线程数+1）
主线程调用await()，等待子线程执行完成
子线程各自进行商品价格的计算，计算完成后，调用await()，等待其他线程也到达barrier point
当所有子线程计算完成后，由于没有后续操作，所以子线程运行结束，同时由于主线程还有后续操作，会先输出提示信息再终止线程池
注意一个很大的不同就是这里的线程池核心线程数目改成了 10，那么，为什么需要10？

因为如果是设置一个小于10的核心线程个数，由于线程池是会先创建核心线程来执行任务，核心线程满了之后，放进任务队列中，而假设只有5个核心线程，那么：

5个线程进行计算价格
另外5个任务放在任务队列中
这样的话，会出现死锁，因为计算中的线程需要队列中的任务到达barrier point才能结束，而队列中的任务需要核心线程计算完毕后，才能调度出来计算，这样死锁就出现了。

参考资料：https://www.cnblogs.com/6b7b5fc3/p/14684697.html
 */
public class ExceptionTest {

    public static void main(String[] args) throws InterruptedException,BrokenBarrierException{


        countDownLatchExample();// CountDownLatch 示例

//        cyclicBarrierExample(); // CyclicBarrier 示例
    }

    /*
        CountDownLatch是一个同步工具类，常见的使用场景包括：

        允许一个或多个线程等待一系列的其他线程结束
        在串行化任务中需要进行并行化处理，并等待所有并行化任务结束，串行化任务才能继续进行
     */
    public static void countDownLatchExample() throws InterruptedException {
        List<Price> list = IntStream.rangeClosed(1,10).mapToObj(Price::new).collect(Collectors.toList());
        //计数器大小为商品列表的长度
        final CountDownLatch latch = new CountDownLatch(list.size());
        //线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        list.forEach(p-> executor.execute(()->{
            System.out.println("Product "+p.id+" start calculate price ");
            try{
                //随机休眠模拟业务操作耗时
                TimeUnit.SECONDS.sleep(current().nextInt(10));
                p.setPrice(p.getPrice()*((p.getId() & 1) == 1 ? 0.9 : 0.7));
                System.out.println("Product "+p.id+" calculate price completed");
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                //每完成计算一个商品，将计数器减1，注意需要放在finally中
                latch.countDown();
            }
        }));
        //主线程阻塞直到所有的计数器为0，也就是等待所有的子任务计算价格完毕
        latch.await();
        System.out.println("All of prices calculate finished");
        //手动终止，不然不会结束运行
        executor.shutdown();
    }

    /*
        CyclicBarrier与CountDownLatch有很多类似的地方，也是一个同步工具类，允许多个线程在执行完相应的操作之后彼此等待到达同一个barrier point（屏障点）。CyclicBarrier也适合某个串行化的任务被拆分为多个并行化任务，这点与CountDownLatch类似，但是CyclicBarrier具备的一个更强大的功能是，CyclicBarrier可以被重复使用。

        等待完成
        先简单说一下CyclicBarrier的实现原理：

        初始化CyclicBarrier，传入一个int参数，表示分片（parites），通常意义上来说分片数就是任务的数量
        同时串行化执行多个任务
        任务执行完成后，调用await()，等待其他线程也到达barrier point
        当所有线程到达后，继续以串行化方式运行任务
        常见的使用方法是设置分片数为任务数+1，这样，可以在主线程中执行await()，等待所有子任务完成。比如下面是使用CyclicBarrier实现同样功能的模拟代码：
     */
    public static void cyclicBarrierExample() throws InterruptedException,BrokenBarrierException{
        List<Price> list = IntStream.rangeClosed(1,10).mapToObj(Price::new).collect(Collectors.toList());
        final CyclicBarrier barrier = new CyclicBarrier(11);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        list.forEach(p-> executor.execute(()->{
            System.out.println("Product "+p.id+" start calculate price ");
            try{
                TimeUnit.SECONDS.sleep(current().nextInt(10));
                p.setPrice(p.getPrice()*((p.getId() & 1) == 1 ? 0.9 : 0.7));
                System.out.println("Product "+p.id+" calculate price completed");
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                try{
                    barrier.await();
                }catch (InterruptedException | BrokenBarrierException e){
                    e.printStackTrace();
                }
            }
        }));
        barrier.await();
        System.out.println("All of prices calculate finished");
        executor.shutdown();
    }

    private static class Price{
        private final int id;
        private double price;

        public Price(int id) {
            this.id = id;
        }

        public int getId() {
            return id;
        }

        public double getPrice() {
            return price;
        }

        public void setPrice(double price) {
            this.price = price;
        }

        public void setPrice2(double price) throws Exception {
            if(price <= 0){
                throw new Exception("");
            }
            this.price = price;
        }
    }

}
